// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#include "pch.h"
#include "ServiceKeys.h"
#include "UserSettings.h"
#include "WeatherImageProvider.h"
#include "WebException.h"
#include "WebServiceRegistry.h"
#include "WWOWeatherService.h"

using namespace Weathr;
using namespace Weathr::Web;

using namespace concurrency;
using namespace Platform;
using namespace std;
using namespace utility;
using namespace web;
using namespace web::http;
using namespace web::http::client;

WeatherType GetWeatherType(int32 weatherCode)
{
    switch (weatherCode)
    {
    case 395: return WeatherType::Snow; // Moderate or heavy snow in area with thunder
    case 392: return WeatherType::Snow; // Patchy light snow in area with thunder
    case 389: return WeatherType::Thunderstorms; // Moderate or heavy rain in area with thunder
    case 386: return WeatherType::Thunderstorms; // Patchy light rain in area with thunder
    case 377: return WeatherType::Snow; // Moderate or heavy showers of ice pellets
    case 374: return WeatherType::Snow; // Light showers of ice pellets
    case 371: return WeatherType::Snow; // Moderate or heavy snow showers
    case 368: return WeatherType::Snow; // Light snow showers
    case 365: return WeatherType::Snow; // Moderate or heavy sleet showers
    case 362: return WeatherType::Snow; // Light sleet showers
    case 359: return WeatherType::Drizzle; // Torrential rain shower
    case 356: return WeatherType::Drizzle; // Moderate or heavy rain shower
    case 353: return WeatherType::Drizzle; // Light rain shower
    case 350: return WeatherType::Snow; // Ice pellets
    case 338: return WeatherType::Snow; // Heavy snow
    case 335: return WeatherType::Snow; // Patchy heavy snow
    case 332: return WeatherType::Snow; // Moderate snow
    case 329: return WeatherType::Snow; // Patchy moderate snow
    case 326: return WeatherType::Snow; // Light snow
    case 323: return WeatherType::Snow; // Patchy light snow
    case 320: return WeatherType::Snow; // Moderate or heavy sleet
    case 317: return WeatherType::Snow; // Light sleet
    case 314: return WeatherType::Snow; // Moderate or Heavy freezing rain
    case 311: return WeatherType::Snow; // Light freezing rain
    case 308: return WeatherType::Drizzle; // Heavy rain
    case 305: return WeatherType::Drizzle; // Heavy rain at times
    case 302: return WeatherType::Drizzle; // Moderate rain
    case 299: return WeatherType::Drizzle; // Moderate rain at times
    case 296: return WeatherType::SlightDrizzle; // Light rain
    case 293: return WeatherType::SlightDrizzle; // Patchy light rain
    case 284: return WeatherType::Snow; // Heavy freezing drizzle
    case 281: return WeatherType::Snow; // Freezing drizzle
    case 266: return WeatherType::SlightDrizzle; // Light drizzle
    case 263: return WeatherType::SlightDrizzle; // Patchy light drizzle
    case 260: return WeatherType::Snow; // Freezing fog
    case 248: return WeatherType::Cloudy; // Fog
    case 230: return WeatherType::Snow; // Blizzard
    case 227: return WeatherType::Snow; // Blowing snow
    case 200: return WeatherType::Thunderstorms; // Thundery outbreaks in nearby
    case 185: return WeatherType::Snow; // Patchy freezing drizzle nearby
    case 182: return WeatherType::Snow; // Patchy sleet nearby
    case 179: return WeatherType::Snow; // Patchy snow nearby
    case 176: return WeatherType::Drizzle; // Patchy rain nearby
    case 143: return WeatherType::MostlyCloudy; // Mist
    case 122: return WeatherType::MostlyCloudy; // Overcast
    case 119: return WeatherType::Cloudy; // Cloudy
    case 116: return WeatherType::MostlyCloudy; // Partly Cloudy
    case 113: return WeatherType::Clear; // Clear/Sunny
    default:  return WeatherType::Unknown;
    }
}

const uint32_t MaxRequestsPerSecond = 2u; // Technically, 3 is allowed, but because the throttler can be in accurate, let's be conservative.

WWOWeatherService::WWOWeatherService() : m_throttler(MaxRequestsPerSecond)
{
    m_throttler.start();
    WebServiceRegistry::RegisterWebService(this);
}

// Updates the weather information associated with the provided Forecast object.
task<void> WWOWeatherService::GetWeatherInfoAsync(Forecast^ forecast)
{
    uri_builder uri(L"http://api.worldweatheronline.com/free/v1/weather.ashx");
    wostringstream fragment;
    fragment << forecast->Latitude << L',' << forecast->Longitude;
    uri.append_query(L"q", fragment.str());
    uri.append_query(L"format", L"json");
    uri.append_query(L"num_of_days", 5);
    uri.append_query(L"key", WWOServiceKey);

    return m_throttler.DownloadAsync(uri.to_uri()).then([](http_response response)
    {
        assert(IsBackgroundThread());

        unsigned long status = response.status_code();
        if (status != 200)
        {
           throw WebConnectionException(status);
        }

        return response.extract_json();

    }).then([forecast](task<json::value> previousTask)
    {
        assert(IsMainThread());

        try
        {
            auto v = previousTask.get();

            const json::value& currentCondition = v[L"data"][L"current_condition"];

            int32_t temp_F = _wtoi(currentCondition[0][L"temp_F"].as_string().c_str());

            vector<WeatherData^> allWeatherData;

            const json::value& weather = v[L"data"][L"weather"];
            for (auto iter = weather.cbegin(); iter != weather.cend(); ++iter)
            {
                const auto& data = iter->second;

                auto dateString = data[L"date"].as_string();

                // TODO: This functionality is broken, but is fixed in the C++ REST SDK version on CodePlex.
                // Below is the workaround. Revisit this later.
                //datetime dt = datetime::from_string(dateString, datetime::ISO_8601);
                //auto date = dt.to_string();

                uint32_t year, month, day;
                swscanf_s(dateString.c_str(), L"%d-%d-%d", &year, &month, &day);
                auto c = ref new Windows::Globalization::Calendar();
                c->ChangeCalendarSystem(Windows::Globalization::CalendarIdentifiers::Gregorian);
                c->Year = year;
                c->Month = month;
                c->Day = day;
                c->Period = 1;
                c->Hour = c->FirstHourInThisPeriod;
                c->Minute = 0;
                c->Second = 0;
                c->Nanosecond = 0;
                auto date = c->GetDateTime();

                auto weatherData = forecast->GetForDate(date);
                if (weatherData == nullptr)
                {
                    weatherData = ref new WeatherData();
                    weatherData->Date = date;
                    weatherData->DebugDate = dateString;
                }

                weatherData->CurrentTemperatureF = temp_F;
                weatherData->Description = ref new String(data[L"weatherDesc"][0][L"value"].as_string().c_str());
                weatherData->ExpectedHiF = _wtoi(data[L"tempMaxF"].as_string().c_str());
                weatherData->ExpectedLoF = _wtoi(data[L"tempMinF"].as_string().c_str());
                weatherData->WindSpeedMph = _wtoi(data[L"windspeedMiles"].as_string().c_str());
                weatherData->WindDirection = ref new String(data[L"winddirection"].as_string().c_str());
                weatherData->WindDirectionDegree = _wtoi(data[L"winddirDegree"].as_string().c_str());
                weatherData->PrecipMM = static_cast<float>(_wtof(data[L"precipMM"].as_string().c_str()));
                weatherData->WeatherType = GetWeatherType(_wtoi(data[L"weatherCode"].as_string().c_str()));
                weatherData->WeatherImagePathUri = ref new String(WeatherImageProvider::GetWeatherImage(weatherData->WeatherType, false).c_str());

                if (UserSettings::Current->IsFahrenheitSet)
                {
                    weatherData->CurrentDisplayTemperature = weatherData->CurrentTemperatureF;
                    weatherData->ExpectedLoDisplayTemperature = weatherData->ExpectedLoF;
                    weatherData->ExpectedHiDisplayTemperature = weatherData->ExpectedHiF;
                    wchar_t buffer[256];
                    swprintf_s(buffer, L"Wind from %s at %d mph", weatherData->WindDirection->Data(), weatherData->WindSpeedMph);
                    weatherData->DisplayWindSpeed = ref new String(buffer);
                }
                else
                {
                    weatherData->CurrentDisplayTemperature = weatherData->CurrentTemperatureC;
                    weatherData->ExpectedLoDisplayTemperature = weatherData->ExpectedLoC;
                    weatherData->ExpectedHiDisplayTemperature = weatherData->ExpectedHiC;
                    wchar_t buffer[256];
                    swprintf_s(buffer, L"Wind from %s at %d km", weatherData->WindDirection->Data(), weatherData->WindSpeedKm);
                    weatherData->DisplayWindSpeed = ref new String(buffer);
                }

                allWeatherData.emplace_back(weatherData);
            }

            forecast->UpdateWeatherData(allWeatherData);
            forecast->LastCheckin = datetime::utc_now();
        }
        catch (const json::json_exception&)
        {
            wostringstream ss;
            ss << L"WWOWeatherService: Failed to parse data for '" << forecast->Locality->Data() << '\'';
            throw WebDataException(ss.str());
        }

    }, task_continuation_context::use_current());
}

// Retrieves the name of the service.
wstring WWOWeatherService::GetServiceName() const
{
    return L"World Weather Online";
}